﻿#if false

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Engine;
using Engine.Attributes;

namespace Engine.Components.CompArch
{
	public class Cache: MemoryBase
    {
        #region Ports
        /// <summary>
        /// Communicates with the IsWrite port of a memory component on a cache miss or writeback
        /// </summary>
        public OutPort<bool> OutgoingIsWrite = new OutPort<bool>();

        /// <summary>
        /// Communicates with the Address port of a memory component on a cache miss or writeback
        /// </summary>
        public OutPort<UInt32> OutgoingAddress = new OutPort<UInt32>();

        /// <summary>
        /// Communicates with the WritePort port of a memory component on a writeback
        /// </summary>
        public OutPort<UInt32> OutgoingWritePort = new OutPort<UInt32>();

        /// <summary>
        /// Communicates with the ReadPort port of a memory component on a cache miss
        /// </summary>
        public InPort<UInt32> IncomingReadPort = new InPort<UInt32>();

        /// <summary>
        /// Communicates with the InvalidAddressFlag port of a memory component on a writeback or cache miss
        /// </summary>
        public InPort<bool> IncomingInvalidAddressFlag = new InPort<bool>();
        
        #endregion

        #region PrivateFields
        // number of address bits denoting the tag
		private UInt32 numTagBits;

		// number of address bits denoting the set;
		private UInt32 numSetBits;

		// number of address bits denoting the cache line offset
        private UInt32 lineOffsetBits;

        // backing variable for CacheLinesPerSet
        private UInt32 cacheLinesPerSet;

        // the actual cache storage
		private CacheLine[,] lines; // [sets,slotsPerSet]

        // masks for splitting up addresses
        private UInt32 tagMask;
        private UInt32 setMask;
        private UInt32 offsetMask;

        #endregion // PrivateFields 

        public Cache()
		{
			base.Size = CacheDefaults.DEFAULT_SIZE;
            base.Alignment = CacheDefaults.DEFAULT_ALIGNMENT;
            this.Geometry = string.Format("{0},{1},{2}", CacheDefaults.DEFAULT_NUM_SET_BITS, CacheDefaults.DEFAULT_NUM_TAG_BITS, CacheDefaults.DEFAULT_NUM_OFFSET_BITS);
            this.CacheLinesPerSet = CacheDefaults.DEFAULT_SET_SIZE;

            this.Address.Run = this.OnAddressSet;
            this.IncomingInvalidAddressFlag.Run = this.OnIncomingInvalidAddressFlagSet;
            this.IsWrite.Run = this.OnIsWriteSet;
        }

        #region InPortDelegates

        /// <summary>
        /// Delegate executed when the Address property is set
        /// </summary>
        private void OnAddressSet()
        {
            this.OutgoingAddress.Value = this.Address.Value;
        }

        /// <summary>
        /// Delegate executed when the IncomingInvalidAddressFlag property is set
        /// </summary>
        private void OnIncomingInvalidAddressFlagSet()
        {
            this.InvalidAddressFlag.Value = this.IncomingInvalidAddressFlag.Value;
        }

        /// <summary>
        /// Delegate executed when the IsWrite property is set
        /// </summary>
        public void OnIsWriteSet()
        {
            UInt32 tag = (this.Address.Value & tagMask) >> (32 - this.numSetBits - this.lineOffsetBits);
            UInt32 set = (this.Address.Value & setMask) >> (32 - this.lineOffsetBits);
            UInt32 offset = (this.Address.Value & offsetMask);

            if (this.IsWrite.Value)
            {
                // if address is already in the cache and is dirty, write it back
                int pos = -1;
                try
                {
                    pos = this.FindSlot(set, tag);
                    
                    // if we made it this far, the line is indeed in the cache
                    CacheLine line = lines[set, pos];
                    
                    // case where line is in the cache but not valid; replace in-line
                    if (!line.IsValid)
                    {
                        // write to cache and quit
                        line.Write(WritePort.Value);
                        return;
                    }

                    // case where line is in the cache but not dirty (probably won't happen but I'll include it in case)
                    if (!line.IsDirty)
                    {
                        // write to cache and quit
                        line.Write(WritePort.Value);
                        return;
                    }

                    // case where the line is in the cache and dirty
                    if (line.IsDirty)
                    {
                        // write to memory first
                        OutgoingAddress.Value = Address.Value;
                        OutgoingWritePort.Value = line.Read();
                        OutgoingIsWrite.Value = true;

                        // write to cache and quit
                        line.Write(WritePort.Value);
                    }
                }
                catch (LineNotInCacheException)
                {
                    // for now, just use optimal LRU
                    pos = FindLruSlot(set);
                    CacheLine replaceMe = lines[set, pos];
                    replaceMe.Write(WritePort.Value);
                }

                // otherwise, find an available slot

                // (over)write the new value
            }
            else
            {
                // if addres is already in the cache, return that value
                // otherwise, fetch from memory, place in cache, then return the value
            }
        }

        #endregion // InPortDelegates

        #region Configurables

        /// <summary>
        /// Specifies the number of slots (cache lines) per set
        /// </summary>
        [Config]
        public UInt32 CacheLinesPerSet 
        {
            get { return this.cacheLinesPerSet; }
            set
            {
                this.cacheLinesPerSet = value;
                Rebuild();
            }
        }

        /// <summary>
        /// The Cache geometry, formatted as numSetBits,numSlotBits,lineOffsetBits
        /// </summary>
        [Config]
        public string Geometry
        {
            get { return String.Format("{0},{1},{2}", numSetBits, numTagBits, lineOffsetBits); }
            set
            {
                // parse out the tokens
                try
                {
                    char[] separator = { ',' };
                    string[] tokens = value.Split(separator);

                    if (tokens.Length != 3) throw new InvalidConfigValueException("Geometry format is \"numSets,numSlots,bytesPerSlot\"");

                    UInt32 sets = UInt32.Parse(tokens[0]);
                    UInt32 tag = UInt32.Parse(tokens[1]);
                    UInt32 bps = UInt32.Parse(tokens[2]);

                    if (sets+tag+bps != 32)
                    {
                        throw new InvalidConfigValueException("Geometry numbers must sum to 32");
                    }

                    // we won't get here unless all the tokens parse correctly
                    this.numSetBits = sets;
                    this.numTagBits = tag;
                    this.lineOffsetBits = bps;

                    // set up the address masks
                    BuildMasks();

                    // initialize the cache geometry
                    Rebuild();
                }
                catch (FormatException)
                {
                    throw new InvalidConfigValueException("A Geometry number has an incorrect format");
                }
                catch (ArgumentNullException)
                {
                    throw new InvalidConfigValueException("A Geometry number was null");
                }
            }
        }
        #endregion

        #region Inspectables
        [Inspectable]
        public int Hits { get; private set; }

        [Inspectable]
        public int TotalRequests { get; private set; }
        #endregion

        #region PrivateHelpers

        private class LineNotInCacheException : Exception { }

        /// <summary>
        /// Finds a line in the cache, if it exists
        /// 
        /// Throws an LineNotInCacheException if the line is not in the cache.
        /// </summary>
        /// <param name="setId">the set number of the line</param>
        /// <param name="tagId">the tag for the line</param>
        /// <returns>the position of the cache line within the set</returns>
        private int FindSlot(UInt32 setId, UInt32 tagId)
        {
            for (int i = 0; i < this.lines.GetLength(0); i++)
            {
                CacheLine line = lines[setId, i];
                if (line.Tag == tagId) return i;
            }

            // we only get here if the tag wasn't found
            throw new LineNotInCacheException();
        }
        
        /// <summary>
        /// Finds the least-recently-used (LRU) cache line
        /// </summary>
        /// <param name="setId">the set to look in</param>
        /// <returns>the position of the LRU slot within the set</returns>
        private int FindLruSlot(UInt32 setId)
        {
            int min = UInt32.MaxValue;
            int pos = -1;
            for(int i=0; i < lines.GetLength(0); i++)
            {
                // if we find an invalid line, just use that
                if (!lines[i].IsValid) return i;

                // find the LRU line
                if(lines[i].Refs < min)
                {
                    min = lines[i].Refs;
                    pos = i;
                }
            }

            return pos;
        }

        /// <summary>
        /// Rebuilds the cache after its config values have changed
        /// </summary>
        private void Rebuild()
        {
            this.lines = new CacheLine[ToPowerOf2(numSetBits), cacheLinesPerSet];
            UInt32 lineSize = ToPowerOf2(lineOffsetBits);
            for (int i = 0; i < lines.GetLength(1); i++)
            {
                for (int j = 0; j < lines.GetLength(0); j++)
                {
                    lines[i, j] = new CacheLine(lineSize);
                }
            }

            Reset();
        }

        /// <summary>
        /// Builds bit-masks
        /// </summary>
        private void BuildMasks()
        {
            UInt32 allOnes = 0xFFFFFFFF;
            tagMask = allOnes << (32 - this.numTagBits);
            offsetMask = ~(allOnes << this.lineOffsetBits);
            setMask = ~(tagMask | offsetMask);
        }
        #endregion

        public override void Reset()
        {
            this.Hits = 0;
            this.TotalRequests = 0;
            foreach (CacheLine line in lines)
            {
                line.ZeroOut();
            }
        }


    }

    struct CacheLine
    {
        /// <summary>
        /// Tag for this cache line
        /// </summary>
        public UInt32 Tag { get; private set; }

        /// <summary>
        /// True if the data in this line is initialized; false if it is undefine
        /// </summary>
        public bool IsValid { get; private set; }

        /// <summary>
        /// True if the data in this line needs to be written back to memory; false otherwise
        /// </summary>
        public bool IsDirty { get; private set; }

        /// <summary>
        /// The number of times this line has been referenced (for LRU replacement policy)
        /// </summary>
        public UInt32 Refs { get; private set; }

        private byte[] line;

        public CacheLine(int length)
        {
            line = new byte[length];
            ZeroOut();
        }

        /// <summary>
        /// Initializes everything in the line to zero; invalidates the line and resets all reference counters
        /// </summary>
        public void ZeroOut()
        {
            IsValid = false;
            IsDirty = false;
            Refs = 0;
            for (int i = 0; i < line.Length; i++) line[i] = 0;
        }

        /// <summary>
        /// Writes a chunk of data to the CacheLine
        /// </summary>
        /// <param name="data">the data to write</param>
        public void Write(byte[] data, UInt32 tag)
        {
            if (line.Length != data.Length)
            {
                throw new Exception("Cache line and written lengths do not match");
            }

            for (int i = 0; i < line.Length; i++) line[i] = data[i];
            this.IsDirty = true;
            this.IsValid = true;
            this.Refs = 1;
            this.Tag = tag;
        }

        /// <summary>
        /// Reads the CacheLine data
        /// </summary>
        /// <param name="offset">the offset of the data within the line</param>
        /// <returns>the requested data within the line</returns>
        public byte[] Read(UInt32 offset, AlignmentBoundary alignment)
        {
            this.Refs += 1;
            byte[] buf = new byte[(int)alignment];
            for (int i = 0; i < alignment; i++)
            {
                buf[i] = line[offset + i];
            }
            return buf;
        }
    }
}

#endif